Skip to content

[实例] 轮播图

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>[React] 轮播图</title>
    <!-- React 核心库,与宿主环境无关 -->
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <!-- 依赖核心库,将核心功能与页面结合 -->
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <!-- babel -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>

    <style>
      .banner {
        border: 1px solid #ccc;
        overflow: hidden;
        position: relative;
      }

      .img-container {
        /* border: 1px solid #fac; */
        float: left;
      }

      .dots {
        align-items: center;
        background-color: rgba(255, 255, 255, 0.5);
        border-radius: 15px;
        bottom: 30px;
        display: flex;
        height: 30px;
        justify-content: space-around;
        left: 50%;
        position: absolute;
        transform: translateX(-50%);
        width: 300px;
        z-index: 1;
      }

      .dots .dot {
        background-color: rgba(255, 255, 255, 0.8);
        border-radius: 50%;
        display: inline-block;
        height: 10px;
        width: 10px;
      }

      .dots .dot.active {
        background-color: #fac;
      }
      .dots .dot:not(.active) {
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    <div id="root"></div>
    <!-- 图片容器组件 -->
    <script type="text/babel">
      class ImgContainer extends React.Component {
        static propTypes = {
          width: PropTypes.number.isRequired,
          height: PropTypes.number.isRequired,
          imgList: PropTypes.arrayOf(PropTypes.string).isRequired,
          duration: PropTypes.number.isRequired,
          autoDuration: PropTypes.number.isRequired,
          autoPlay: PropTypes.bool.isRequired,
        };

        static defaultProps = {
          width: 800,
          height: 600,
          imgList: [],
          duration: 500,
          autoDuration: 500,
          autoPlay: true,
        };

        state = {
          index: 0,
          marginLeft: 0,
        };
        autoTimer = null; // 自动播放定时器
        tickTimer = null; // 间隔定时器
        tick = 16; // 定时器间隔

        containerRef = (el) => {
          this.container = el;
        };
        /**
         * 切换图片
         * @returns
         */
        switchImg = (index) => {
          // 1. 计算最终index 的marginLeft
          const targetMarginLeft = -this.props.width * index;
          // 2. 获取当前marginLeft
          let currentMarginLeft = +window
            .getComputedStyle(this.container)
            .marginLeft.split("px")[0];
          // 3. 计算运动次数
          const times = Math.ceil(this.props.autoDuration / this.tick);
          let curTime = 0;
          // 4. 移动总距离
          const totalDis = targetMarginLeft - currentMarginLeft;
          const stepDis = totalDis / times;
          //
          clearInterval(this.tickTimer);
          // 5. 开始移动
          this.tickTimer = setInterval(() => {
            curTime++;
            currentMarginLeft += stepDis;
            this.setState({
              marginLeft: currentMarginLeft,
            });
            // 边界
            if (curTime === times) {
              clearInterval(this.tickTimer);
              this.setState({
                marginLeft: targetMarginLeft,
              });
              return;
            }
          }, this.tick);
        };

        /**
         * 自动播放
         */
        autoPlay = () => {
          clearInterval(this.autoTimer);
          let direction = 1; // 1: 向右, -1: 向左
          this.autoTimer = setInterval(() => {
            let index = this.state.index;
            if (index <= 0) {
              direction = 1;
            }
            if (index >= this.props.imgList.length - 1) {
              direction = -1;
            }
            this.setState({
              index: index + direction,
            });
            this.switchImg(this.state.index);
          }, this.props.duration);
        };
        /**
         * 点击dot
         */
        handleClickDot = (e) => {
          if (e.target.className.includes("active")) {
            e.stopPropagation();
          }
          const index = +e.target.dataset.index;
          this.setState({
            index,
          });
          this.switchImg(index);
        };

        componentDidMount() {
          if (this.props.autoPlay) {
            this.autoPlay();
          }
        }

        render() {
          return (
            <>
              <div
                ref={this.containerRef}
                className="img-container"
                style={{
                  width: this.props.width * this.props.imgList.length,
                  height: this.props.height,
                  marginLeft: this.state.marginLeft + "px",
                }}
              >
                {this.props.imgList.map((item, index) => (
                  <img
                    key={index}
                    src={item}
                    alt=""
                    style={{
                      width: this.props.width,
                      height: this.props.height,
                    }}
                  />
                ))}
              </div>
              <div
                className="dots"
                style={{ width: 50 * this.props.imgList.length + "px" }}
              >
                {this.props.imgList.map((item, index) => (
                  <span
                    key={index}
                    data-index={index}
                    className={
                      this.state.index === index ? "dot active" : "dot"
                    }
                    onClick={this.handleClickDot}
                  ></span>
                ))}
              </div>
            </>
          );
        }
      }
    </script>
    <!-- 轮播图组件 -->
    <script type="text/babel">
      class Banner extends React.Component {
        //
        static propTypes = {
          //
          width: PropTypes.number.isRequired,
          height: PropTypes.number.isRequired,
          duration: PropTypes.number.isRequired,
          autoDuration: PropTypes.number.isRequired,
          autoPlay: PropTypes.bool.isRequired,
          imgList: PropTypes.arrayOf(PropTypes.string).isRequired,
        };

        static defaultProps = {
          width: 800,
          height: 600,
          duration: 3000,
          autoDuration: 500,
          autoPlay: true,
          imgList: [
            "https://fastly.picsum.photos/id/25/800/600.jpg?hmac=c3XP15iyQtTasbi_voCiOB1nHCq0O-mET-ccc6UZgqI",
            "https://fastly.picsum.photos/id/28/800/600.jpg?hmac=XlKXM-KYpKBEaXsPpMCtXiI1-YlgaN5DDmAAhbFE5h8",
            "https://fastly.picsum.photos/id/29/800/600.jpg?hmac=KbDC2qhBvoFM4XpOZrAnybO4JNiXS9mox0PORy6NCJA",
            "https://fastly.picsum.photos/id/33/800/600.jpg?hmac=wsfu4HjxsdEud_jpRFwK2l0D-PTjHAJES3RyYvGHHHU",
          ],
        };

        render() {
          return (
            <div
              className="banner"
              style={{ width: this.props.width, height: this.props.height }}
            >
              <ImgContainer
                width={this.props.width}
                height={this.props.height}
                imgList={this.props.imgList}
                duration={this.props.duration}
                autoDuration={this.props.autoDuration}
                autoPlay={this.props.autoPlay}
              />
            </div>
          );
        }
      }
    </script>
    <script type="text/babel">
      ReactDOM.render(<Banner />, document.getElementById("root"));
    </script>
  </body>
</html>

Released under the MIT License.